#!/usr/bin/python
#***************************************************************************
# Copyright notice for this file:
#  Copyright (C) 2012 TheAxeMan
#  _______ _                               _    _
# |__   __| |     ___     /\          ___ | \  / |
#    | |  | |__  / _ \   /  \  __  _ / _ \|  \/  | __ _ _ __
#    | |  | '_ \|  __/  / /\ \ \ \/ |  __/| .  . |/ _` | '_ \
#    | |  | | | | (__/\/ ____ \ )  (| (__/| |\/| | (_| | | | |
#    |_|  |_| |_|\____/_/    \_._/\_.\____|_|  |_|\__,_|_| |_|
#
# Final Fantasy TAS scripts by TheAxeMan
#
# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation; either version 2 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
#
#***************************************************************************

import sys
import re
import string

"""
The GameData class holds information on how the encounter engine works. 
Once initialized a GameData object "gdata" will have the following contents:

gdata.threshold_list - list with the contents of the table used to determine
                       what step an encounter happens
gdata.group_order - the order that the 8 different groups appear in
gdata.group_info - dict matching enemy group to list of enemies in that group

gdata.enemy_groups - list of enemy group names
gdata.enemy_group_ids - dict matching group names to index in gdata.enemy_groups

gdata.areas - list of area names
gdata.area_ids - dict matching area names to index in gdata.areas

gdata.noRunList - list of group names that are not runnable
gdata.shadowList - list of shadow group names
"""

class GameData:

  def getEncRandList(self, filename):
    """gets a list of the random numbers that drive the encounter engine"""
    file = open(filename)
    lineread = 0
    randNums = []
    for line in file:
      if re.search("Below is the table", line):
        lineread += 1
      elif lineread > 0:
        lineread += 1
      
      if lineread >= 3:
        nums = line.split()
        for num in nums:
          randNums.append(int(num, 16))
  
      if len(randNums) == 256: break
  
    file.close()
    return randNums
  
  def getEncGroupOrder(self, filename):
    """returns a list with the enemy group number order (1-8)"""
    file = open(filename)
    randNums = range(256)
    for line in file:
      if not re.search("^\d(\d)?\:", line): continue
      if randNums[0] > 0 and re.search("^\s", line): break
      line = re.sub("\:", "", line)
      nums = line.split()
      for i in range(0,8):
        randNums[int(nums[2*i])-1] = int(nums[2*i+1])
    file.close()
    return randNums
  
  def getEnemiesInArea(self, filename):
    """returns a dict: keys are areas, value 
    is a list of enemy groups for that area"""
    file = open(filename)
    line_num = 0
    area_enemies = {}
    for line in file:
      line_num += 1
      if line_num >= 3795 and line_num <= 3903:
        (area, enemy_groups) = line.split("|")
        area = area.strip()
        enemy_groups = enemy_groups.strip()
        enemy_groups = enemy_groups.split()
        area_enemies[area] = enemy_groups
      elif line_num > 3903: break
    file.close()
    return area_enemies
  
  def getEnemyGroupInfo(self, filename):
    """returns a dict: keys are enemy group ids, value
    is a list of enemies in that group"""
    file = open(filename)
    enemy_groups = {}
    four_line_groups_read = 0
    group_lines_read = 0
    parse_line = False
    temp_groups = range(4)
    for line in file:
  
      if re.search("^(0a|0b)", line):
        parse_line = True
      if not parse_line: continue
  
      group_lines_read += 1
      for i in range(4):
        if group_lines_read == 1:
          enemy_group = line[i*20:i*20+3].strip()
          enemy_groups[enemy_group] = []
          temp_groups[i] = enemy_group
          if enemy_group == "": sys.exit(0)
        enemy = line[i*20+3:i*20+18].strip()
        if enemy != "Nothing":
          enemy_groups[temp_groups[i]].append(enemy)
  
      if group_lines_read == 4:
        #after finishing four lines, reset group_lines counter
        #and increment four_line_groups_counter
        group_lines_read = 0
        four_line_groups_read += 1
        #if whole section is finished, reset for next section
        if four_line_groups_read == 32:
          four_line_groups_read = 0
          parse_line = False
    #define a Nothing group for empty groups
    enemy_groups["Nothing"] = []
  
    file.close()
    return enemy_groups
  
  def loadData(self):
    enc_info_file = "enc_info.txt"
    char_list_file = "final_fantasy_char_list.txt"
    #list of numbers for encounter thresholds
    self.threshold_list = self.getEncRandList(enc_info_file)
    #enemy group (1-8) order
    self.group_order = self.getEncGroupOrder(char_list_file)
    #enemies in each area
    area_enemies = self.getEnemiesInArea(char_list_file)
    #enemies in each group
    self.group_info = self.getEnemyGroupInfo(char_list_file)
    #no run fights
    self.makeNoRunList()
    #shadow fights
    self.makeShadowList()
    
    #make tables for areas and enemy groups
    #use indexes to speed things up later
    #keep lists and hashes to match them up

    #list of area names
    self.areas = area_enemies.keys()
    #dict matching area names to list index
    self.area_ids = {}
    for i in range(len(self.areas)):
      self.area_ids[self.areas[i]] = i

    #for convenience, add some extra items to the area_ids lookup table
    #all-lower-case version, all lower-case with no whitespace
    for area in self.areas:
      area_id = self.area_ids[area]
      area_lowercase = string.lower(area)
      area_lowercase_nowhitespace = re.sub("\s", "", area_lowercase)
      #print "adding names:", area_lowercase, area_lowercase_nowhitespace
      self.area_ids[area_lowercase] = area_id
      self.area_ids[area_lowercase_nowhitespace] = area_id
    #"sea" or "ocean" is convenient
    self.area_ids["sea"] = self.area_ids["all sea battles"]
    self.area_ids["ocean"] = self.area_ids["all sea battles"]
    #outer world for areas where it doesn't matter
    self.area_ids["outerworld"] = self.area_ids["world map(4,4)"]
    self.area_ids["nearmelmond"] = self.area_ids["world map(2,5)"]
    self.area_ids["nearlefein"] = self.area_ids["world map(7,3)"]
    self.area_ids["templeoffiends"] = self.area_ids["temple of fiend"]
    self.area_ids["tofr1f"] = self.area_ids["tofrevisited1f"]
    self.area_ids["tofr2f"] = self.area_ids["tofrevisited2f"]
    self.area_ids["tofr3f"] = self.area_ids["tofrevisited3f"]
    self.area_ids["tofrair"] = self.area_ids["tof:rair"]
    self.area_ids["tofrearth"] = self.area_ids["tof:rearth"]
    self.area_ids["tofrfire"] = self.area_ids["tof:rfire"]
    self.area_ids["tofrwater"] = self.area_ids["tof:rwater"]
    for flr in ('b1','b2','b3','b4','b5'):
      self.area_ids["gurgu"+flr] = self.area_ids["gurguvolcano"+flr]


    #list of enemy groups
    self.enemy_groups = self.group_info.keys()
    #dict matching enemy group name to list index
    self.enemy_group_ids = {}
    for i in range(len(self.enemy_groups)):
      self.enemy_group_ids[self.enemy_groups[i]] = i

    #list of lists: first index is area, second is list of enemy groups
    self.area_groups = range(len(self.areas))
    for area in area_enemies:
      area_id = self.area_ids[area]
      self.area_groups[area_id] = []
      for enemy_group in area_enemies[area]:
        enemy_group_id = self.enemy_group_ids[enemy_group]
        self.area_groups[area_id].append(enemy_group_id)

    #encounter thresholds for each area
    #listed using same index as areas
    self.area_thresholds = range(len(self.areas))
    for area in self.areas:
      area_id = self.area_ids[area]
      if re.search("World Map", area):
        self.area_thresholds[area_id] = 10
      elif area == "S. Half Rivers":
        self.area_thresholds[area_id] = 10
      elif area == "N. Half Rivers":
        self.area_thresholds[area_id] = 10
      elif area == "All Sea Battles":
        self.area_thresholds[area_id] = 3
      elif area == "Sky Palace 5F":
        self.area_thresholds[area_id] = 24
      elif area == "ToF:R Earth":
        self.area_thresholds[area_id] = 9
      elif area == "ToF:R Fire":
        self.area_thresholds[area_id] = 10
      elif area == "ToF:R Water":
        self.area_thresholds[area_id] = 11
      elif area == "ToF:R Air":
        self.area_thresholds[area_id] = 12
      else:
        self.area_thresholds[area_id] = 8

    return self

  def translateAreaName(self, area_name):
    """translates a string containing an area name to an area id number"""
    if area_name in self.area_ids:
      area_id = self.area_ids[area_name]
    else:
      fix_area_name = string.lower(area_name)
      fix_area_name = fix_area_name.strip()
      fix_area_name = re.sub("\s", "", fix_area_name)
      if fix_area_name not in self.area_ids:
        print "unknown area:", area_name
        print "checked", "'"+area_name+"'", "'"+fix_area_name+"'"
        print "all areas:"
        all_areas = self.area_ids.keys()
        all_areas.sort()
        for a_name in all_areas:
          print a_name
        sys.exit(0)
      else:
        area_id = self.area_ids[fix_area_name]
    return area_id

  def getAreaName(self, area_id):
    return self.areas[area_id]

  def printRawData(self):
    """prints raw data stored in class"""
    print "areas:", self.areas
    print "allowable area names:", self.area_ids.keys()
    print "enemy groups:", self.enemy_groups
    print "encounter threshold randoms list:", self.threshold_list
    print "encounter group order random list:", self.group_order
    print "Enemy groups in each area:", self.area_groups
    print "Enemies in each group:", self.group_info
    print "encounter thresholds for each area:", self.area_thresholds
    return

  def printData(self):
    """prints data with indexed data translated"""
    print "encounter threshold randoms list:", self.threshold_list
    print "encounter group order random list:", self.group_order
    print "Enemies in each group:", self.group_info
    
    #here we need to recompose the hash
    area_enemies = {}
    for area_id in range(len(self.areas)):
      area = self.areas[area_id]
      area_enemies[area] = []
      for enemy_group_id in self.area_groups[area_id]:
        enemy_group = self.enemy_groups[enemy_group_id]
        area_enemies[area].append(enemy_group)
    print "Enemy groups in each area:", area_enemies

    #give the area name with the threshold
    for area in self.areas:
      print area, self.area_thresholds[self.area_ids[area]]

    return

  def makeNoRunList(self):
    #pulled this list from ffhackster
    tempNoRunList = ['1C','21','22','27','2E','32','46','49','4B','4E','57','59','69','73','74','75','76','77','78','79','7A','7B','7C','7D','7E','7F']
    self.noRunList = []
    for group in tempNoRunList:
      self.noRunList.append(group + 'a')
      self.noRunList.append(group + 'b')
    return

  def makeShadowList(self):
    self.shadowList = ['Aa','Ab']

  def prettyPrint(self):
    """nice looking list of all enemies groups in all areas"""
    #make a sorted version of areas list
    #be sure to copy so original isn't modified
    sorted_areas = self.areas[:]
    sorted_areas.sort()
    for area in sorted_areas:
      area_id = self.area_ids[area]
      print "Enemy groups in", area
      for group_id in self.area_groups[area_id]:
        group = self.enemy_groups[group_id]
        print "  " + group + ": " + ", ".join(self.group_info[group])
    return

def main():
  gdata = GameData()
  gdata.loadData()
  gdata.printRawData()
  #gdata.prettyPrint()

if __name__ == "__main__":
  main()

